package com.suprema.biomini;

import idv.kentYeh.util.ArraySupplierWrapper;
import java.io.File;
import javax.swing.filechooser.FileFilter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Kent Yeh
 */
public class DemoMain extends javax.swing.JFrame implements CaptureListener {

    protected static Logger logger = LoggerFactory.getLogger(DemoMain.class);
    private SFRDevice device;
    private AtomicBoolean inEnrolling = new AtomicBoolean(false);
    private List<Template> temps = new ArrayList<Template>();
    private javax.swing.JCheckBox extMode = new javax.swing.JCheckBox("自動附檔名(x)");
    private TempPanel tp = new TempPanel();
    private FileFilter tempFilter = new FileFilter() {

        @Override
        public boolean accept(File f) {
            return f.isDirectory() || f.getName().toLowerCase().endsWith(".dat");
        }

        @Override
        public String getDescription() {
            return "指紋檔案(*.dat)";
        }
    };
    private FileFilter gifFilter = new FileFilter() {

        @Override
        public boolean accept(File f) {
            return f.isDirectory() || f.getName().toLowerCase().endsWith(".gif");
        }

        @Override
        public String getDescription() {
            return "指紋圖形(*.gif)";
        }
    };

    /** Creates new form DemoMain */
    public DemoMain(SFRDevice device) {
        super();
        this.device = device;
        initComponents();
        initDevice();

        DeviceType nowType = device.getDeviceType();
        for (DeviceType dt : DeviceType.values()) {
            cbType.addItem(dt);
            if (nowType.equals(dt)) {
                cbType.setSelectedItem(dt);
            }
        }
        device.addCaptureListener(this);
        extMode.setSelected(true);
        extMode.setMnemonic('x');
        setLocationRelativeTo(getRootPane());
    }

    private synchronized void addLog(String msg) {
        if (!msgArea.getText().isEmpty()) {
            msgArea.append("\n");
        }
        msgArea.append(String.format("%1$tT.%1$tL: %2$s", Calendar.getInstance(), msg));
        msgArea.setCaretPosition(msgArea.getText().length());
    }

    private void initDevice() {
        if (device.getDeviceType().equals(DeviceType.SFR_NONE)) {
            addLog("沒有設備連接");
            cbType.setSelectedIndex(-1);
        } else {
            cbType.setSelectedItem(device.getDeviceType().toString());
            int val1 = device.getTimeout() / 1000;
            val1 = val1 > 10 ? 10 : val1 < 0 ? 0 : val1;
            cbTimeout.setSelectedIndex(val1);
            val1 = device.getDeviceQuantity();
            cbMachine.removeAllItems();
            int val2 = device.getDeviceIndex();
            for (int i = 0; i < val1; i++) {
                cbMachine.addItem(i);
                if (i == val2) {
                    cbMachine.setSelectedIndex(i);
                }
            }
            tfSerialNo.setText(device.getSerialNo());
            chkFastMode.setSelected(SFRDevice.isFastMode());
            spinBright.setValue(device.getBrightness());
            spinSensity.setValue(device.getSensitivity());
        }
    }

    public void onCapture(CaptureEvent evt) {
        logger.trace("事件:掃瞄{}", evt.isIncapture() ? "開始" : "結束");
        if (!evt.isIncapture()) {
            btnClear.setEnabled(device.getImageWidth() > 0);
        }
        if (!evt.isIncapture() && !inEnrolling.get()) {
            btnAddTemp.setEnabled(true);
            btnEnroll.setEnabled(true);
            btnIdentity.setEnabled(true);
            btnSaveImage.setEnabled(true);
            btnSaveTemplate.setEnabled(true);
            btnTest.setEnabled(true);
            btnVerify.setEnabled(true);
        }
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        match = new ImageIcon(getClass().getResource("ok.gif"));
        wrong = new ImageIcon(getClass().getResource("wrong.gif"));
        jLabel1 = new javax.swing.JLabel();
        cbType = new javax.swing.JComboBox();
        jLabel2 = new javax.swing.JLabel();
        cbTimeout = new javax.swing.JComboBox();
        jLabel3 = new javax.swing.JLabel();
        cbMachine = new javax.swing.JComboBox();
        jLabel4 = new javax.swing.JLabel();
        tfSerialNo = new javax.swing.JTextField();
        jLabel5 = new javax.swing.JLabel();
        cbSecurityLavel = new javax.swing.JComboBox();
        chkDetectCore = new javax.swing.JCheckBox();
        chkFastMode = new javax.swing.JCheckBox();
        enrollPanel = new javax.swing.JPanel();
        btnEnroll = new javax.swing.JButton();
        chkDoubleCheck = new javax.swing.JCheckBox();
        jLabel7 = new javax.swing.JLabel();
        cbQuality = new javax.swing.JComboBox();
        jLabel6 = new javax.swing.JLabel();
        cbTemplates = new javax.swing.JComboBox();
        btnAddTemp = new javax.swing.JButton();
        jPanel1 = new javax.swing.JPanel();
        btnVerify = new javax.swing.JButton();
        iconVerify = new javax.swing.JLabel();
        jPanel2 = new javax.swing.JPanel();
        chkThread = new javax.swing.JCheckBox();
        btnIdentity = new javax.swing.JButton();
        iconIdentity = new javax.swing.JLabel();
        btnSaveImage = new javax.swing.JButton();
        btnSaveTemplate = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        msgArea = new javax.swing.JTextArea();
        fingerCanvas = new FingerCanvas(device);
        btnTest = new javax.swing.JButton();
        jLabel8 = new javax.swing.JLabel();
        spinBright = new javax.swing.JSpinner();
        spinBright.setModel(new SpinnerNumberModel(0, 0, 255, 1));
        jLabel9 = new javax.swing.JLabel();
        spinSensity = new javax.swing.JSpinner();
        spinSensity.setModel(new SpinnerNumberModel(0, 0, 7, 1));
        tempChooser = new javax.swing.JFileChooser();
        tempChooser.setCurrentDirectory(new File("."));
        button1 = new java.awt.Button();
        btnClear = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("SFR300v2測試程式");
        setName("mainFrame"); // NOI18N
        setResizable(false);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        jLabel1.setDisplayedMnemonic('a');
        jLabel1.setLabelFor(cbType);
        jLabel1.setText("型號(a)");

        cbType.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cbTypeActionPerformed(evt);
            }
        });

        jLabel2.setDisplayedMnemonic('s');
        jLabel2.setLabelFor(cbTimeout);
        jLabel2.setText("等待逾時秒數(s)");
        jLabel2.setToolTipText("指紋機亮燈等待手指或是辨識的最長等待秒數");

        cbTimeout.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "0", "1", "2", "3", "4", "5*", "6", "7", "8", "9", "10" }));
        cbTimeout.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cbTimeoutActionPerformed(evt);
            }
        });

        jLabel3.setDisplayedMnemonic('m');
        jLabel3.setLabelFor(cbMachine);
        jLabel3.setText("機別(m)");

        cbMachine.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cbMachineActionPerformed(evt);
            }
        });

        jLabel4.setText("序號");

        tfSerialNo.setEditable(false);

        jLabel5.setDisplayedMnemonic('u');
        jLabel5.setLabelFor(cbSecurityLavel);
        jLabel5.setText("安全等級(u)");
        jLabel5.setToolTipText("設定鍣誤接受率(FAR)");

        cbSecurityLavel.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1 (FAR 1/100)", "2 (1/1,000)", "3 (1/10,000)", "4*(1/100,000)", "5 (1/1,000,000)", "6 (1/10,000,000)", "7 (1/100,000,000)" }));
        cbSecurityLavel.setSelectedIndex(3);

        chkDetectCore.setMnemonic('c');
        chkDetectCore.setText("缺損偵測(c)");
        chkDetectCore.setToolTipText("解析指紋時是否檢查中心或四周沒有掃瞄到");

        chkFastMode.setMnemonic('k');
        chkFastMode.setText("快速模式(k)");
        chkFastMode.setToolTipText("加速1:N比對，但會降低辨識率");
        chkFastMode.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                chkFastModeActionPerformed(evt);
            }
        });

        enrollPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("註冊"));

        btnEnroll.setMnemonic('r');
        btnEnroll.setText("登錄(r)");
        btnEnroll.setToolTipText("登錄新的指紋");
        btnEnroll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnEnrollActionPerformed(evt);
            }
        });

        chkDoubleCheck.setMnemonic('d');
        chkDoubleCheck.setSelected(true);
        chkDoubleCheck.setText("再確認(d)");
        chkDoubleCheck.setToolTipText("登錄指紋時，須再掃瞄一次進行確認");

        jLabel7.setDisplayedMnemonic('q');
        jLabel7.setLabelFor(cbQuality);
        jLabel7.setText("最低容忍掃瞄品質(q)");

        cbQuality.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "30", "40", "50*", "60", "70" }));
        cbQuality.setSelectedIndex(2);

        jLabel6.setText("儲存指紋");

        btnAddTemp.setMnemonic('l');
        btnAddTemp.setText("載入(l)");
        btnAddTemp.setToolTipText("載入之前已存之指紋資料檔案");
        btnAddTemp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAddTempActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout enrollPanelLayout = new javax.swing.GroupLayout(enrollPanel);
        enrollPanel.setLayout(enrollPanelLayout);
        enrollPanelLayout.setHorizontalGroup(
            enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(enrollPanelLayout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(btnAddTemp)
                    .addComponent(btnEnroll))
                .addGroup(enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(enrollPanelLayout.createSequentialGroup()
                        .addGap(18, 18, 18)
                        .addComponent(chkDoubleCheck)
                        .addGap(18, 18, 18)
                        .addComponent(jLabel7)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cbQuality, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(enrollPanelLayout.createSequentialGroup()
                        .addGap(21, 21, 21)
                        .addComponent(jLabel6)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cbTemplates, javax.swing.GroupLayout.PREFERRED_SIZE, 279, javax.swing.GroupLayout.PREFERRED_SIZE))))
        );
        enrollPanelLayout.setVerticalGroup(
            enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(enrollPanelLayout.createSequentialGroup()
                .addGroup(enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnAddTemp)
                    .addComponent(jLabel6)
                    .addComponent(cbTemplates, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(enrollPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jLabel7)
                    .addComponent(cbQuality, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(btnEnroll)
                    .addComponent(chkDoubleCheck))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("1:1比對"));

        btnVerify.setMnemonic('v');
        btnVerify.setText("比對(v)");
        btnVerify.setToolTipText("選擇已存指紋後，掃瞄指紋以比對");
        btnVerify.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnVerifyActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addGap(34, 34, 34)
                .addComponent(btnVerify)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(iconVerify)
                .addContainerGap(25, Short.MAX_VALUE))
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(btnVerify)
                    .addComponent(iconVerify))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder("1:N比對"));

        chkThread.setMnemonic('x');
        chkThread.setText("執行緒(x)");
        chkThread.setToolTipText("以多執行緒進行辨識(實測似會降低辨識率)");

        btnIdentity.setMnemonic('i');
        btnIdentity.setText("辨識(i)");
        btnIdentity.setToolTipText("掃瞄指紋與已存指紋相比對");
        btnIdentity.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnIdentityActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(chkThread)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(btnIdentity)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(iconIdentity)
                .addContainerGap(106, Short.MAX_VALUE))
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(chkThread)
                    .addComponent(btnIdentity)
                    .addComponent(iconIdentity))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        btnSaveImage.setMnemonic('g');
        btnSaveImage.setText("存圖(g)");
        btnSaveImage.setToolTipText("儲存已掃瞄之指紋影像(若沒有則先啟動指紋掃瞄)");
        btnSaveImage.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnSaveImageActionPerformed(evt);
            }
        });

        btnSaveTemplate.setMnemonic('f');
        btnSaveTemplate.setText("另存指紋(f)");
        btnSaveTemplate.setToolTipText("儲存已登載之指紋(若沒有則請先登錄指紋)");
        btnSaveTemplate.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnSaveTemplateActionPerformed(evt);
            }
        });

        msgArea.setColumns(20);
        msgArea.setRows(5);
        jScrollPane1.setViewportView(msgArea);

        btnTest.setMnemonic('t');
        btnTest.setText("測試(t)");
        btnTest.setToolTipText("測試掃瞄指紋影像");
        btnTest.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnTestActionPerformed(evt);
            }
        });

        jLabel8.setDisplayedMnemonic('b');
        jLabel8.setLabelFor(spinBright);
        jLabel8.setText("亮度(b,0~255)");

        spinBright.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                spinBrightStateChanged(evt);
            }
        });

        jLabel9.setDisplayedMnemonic('y');
        jLabel9.setLabelFor(spinSensity);
        jLabel9.setText("敏感度(y)");

        spinSensity.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                spinSensityStateChanged(evt);
            }
        });

        tempChooser.setDialogTitle("請選擇已儲存的指紋");
        tempChooser.setFileFilter(tempFilter);

        button1.setLabel("button1");

        btnClear.setIcon(new javax.swing.ImageIcon(getClass().getResource("/com/suprema/biomini/wrong.gif"))); // NOI18N
        btnClear.setMnemonic('e');
        btnClear.setToolTipText("清除掃瞄影像資料");
        btnClear.setEnabled(false);
        btnClear.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnClearActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(13, Short.MAX_VALUE)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cbType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLabel3)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cbMachine, javax.swing.GroupLayout.PREFERRED_SIZE, 56, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 29, Short.MAX_VALUE)
                        .addComponent(jLabel4)))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(cbTimeout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(tfSerialNo, javax.swing.GroupLayout.PREFERRED_SIZE, 277, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(52, 52, 52))
            .addGroup(layout.createSequentialGroup()
                .addGap(14, 14, 14)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(38, 38, 38)
                        .addComponent(fingerCanvas, javax.swing.GroupLayout.PREFERRED_SIZE, 392, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(21, 21, 21)
                        .addComponent(btnClear, 0, 0, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(btnTest)
                        .addGap(71, 71, 71)
                        .addComponent(jLabel8)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(spinBright, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(85, 85, 85)
                        .addComponent(jLabel9)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(spinSensity, javax.swing.GroupLayout.PREFERRED_SIZE, 58, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jLabel5)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(cbSecurityLavel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(chkDetectCore)
                .addGap(18, 18, 18)
                .addComponent(chkFastMode)
                .addContainerGap(91, Short.MAX_VALUE))
            .addGroup(layout.createSequentialGroup()
                .addGap(51, 51, 51)
                .addComponent(btnSaveImage)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 156, Short.MAX_VALUE)
                .addComponent(btnSaveTemplate)
                .addGap(129, 129, 129))
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 448, Short.MAX_VALUE)
                            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                        .addGap(46, 46, 46))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(enrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addContainerGap(46, Short.MAX_VALUE))))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(jLabel1)
                    .addComponent(cbType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel2)
                    .addComponent(cbTimeout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(jLabel3)
                    .addComponent(cbMachine, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel4)
                    .addComponent(tfSerialNo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(jLabel5)
                    .addComponent(cbSecurityLavel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(chkDetectCore)
                    .addComponent(chkFastMode))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(enrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnSaveImage)
                    .addComponent(btnSaveTemplate))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(btnClear)
                    .addComponent(fingerCanvas, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(22, 22, 22)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                    .addComponent(btnTest)
                    .addComponent(jLabel8)
                    .addComponent(spinBright, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jLabel9)
                    .addComponent(spinSensity, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void btnVerifyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnVerifyActionPerformed
        if (temps.isEmpty()) {
            addLog("比對:請先載入指紋資料或登錄指紋資料");
        } else if (cbTemplates.getSelectedItem() == null) {
            iconVerify.setIcon(null);
            addLog("比對:請先選指要儲存的指紋資料");
            cbTemplates.requestFocus();
        } else if (inEnrolling.get()) {
            addLog("比對:登錄進行中，無法執行比對");
        } else {
            iconVerify.setIcon(null);
            Thread action = new Thread() {

                @Override
                public void run() {
                    device.abortCapturing();
                    btnVerify.setEnabled(false);
                    addLog("比對:請放入手指");
                    if (device.capture()) {
                        try {
                            fingerCanvas.repaint();
                            SecurityLevel sl = SecurityLevel.values()[cbSecurityLavel.getSelectedIndex()];
                            long start = System.currentTimeMillis();
                            device.verifyWithError((Template) cbTemplates.getSelectedItem(), sl, chkDetectCore.isSelected());
                            start = System.currentTimeMillis() - start;
                            addLog("比對:合格(品質:" + device.getEnrollQuality() + "),共費 " + start + " ms");
                            iconVerify.setIcon(match);
                        } catch (EnrollException ex) {
                            logger.error(ex.getMessage(), ex);
                            addLog(ex.getMessage());
                            iconVerify.setIcon(wrong);
                        }
                    } else {
                        addLog("比對:掃瞄失敗");
                    }
                }
            };
            action.start();
        }
    }//GEN-LAST:event_btnVerifyActionPerformed

    private void spinBrightStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinBrightStateChanged
        SpinnerModel model = spinBright.getModel();
        if (model instanceof SpinnerNumberModel) {
            if (device.setBrightness(((SpinnerNumberModel) model).getNumber().intValue()) && isVisible()) {
                addLog("亮度:變更");
            }
        }
    }//GEN-LAST:event_spinBrightStateChanged

    private void spinSensityStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinSensityStateChanged
        SpinnerModel model = spinBright.getModel();
        if (model instanceof SpinnerNumberModel) {
            if (device.setSensitivity(((SpinnerNumberModel) model).getNumber().intValue()) && isVisible()) {
                addLog("敏感度:變更");
            }
        }
    }//GEN-LAST:event_spinSensityStateChanged

    private void btnTestActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTestActionPerformed
        if (inEnrolling.get()) {
            addLog("測試: 登錄進行中，無法進行測試");
        } else {
            device.abortCapturing();
            logger.debug("測試: 進行掃圖");
            device.clear();
            addLog("測試: 請放上手指，進行掃瞄");
            Thread action = new Thread() {

                @Override
                public void run() {
                    btnTest.setEnabled(false);
                    addLog(device.capture() ? "測試: 掃瞄成功" : "測試: 掃瞄失敗");
                    fingerCanvas.repaint();
                }
            };
            action.start();
        }
    }//GEN-LAST:event_btnTestActionPerformed

    private void cbTimeoutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTimeoutActionPerformed
        if (isVisible()) {
            int idx = cbTimeout.getSelectedIndex();
            if (device.setTimeout(idx * 1000)) {
                addLog("設定等待秒數成功");
            } else {
                addLog("設定秒數失敗");
                device.setTimeout(device.getTimeout() / 1000 * 1000);
            }
        }
    }//GEN-LAST:event_cbTimeoutActionPerformed

    private void cbMachineActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbMachineActionPerformed
        device.setDeviceIndex(cbMachine.getSelectedIndex());
        tfSerialNo.setText(device.getSerialNo());
    }//GEN-LAST:event_cbMachineActionPerformed

    private void btnAddTempActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddTempActionPerformed
        tempChooser.setDialogType(JFileChooser.OPEN_DIALOG);
        tempChooser.setDialogTitle("請選擇已儲存的指紋");
        tempChooser.setMultiSelectionEnabled(true);
        tempChooser.resetChoosableFileFilters();
        tempChooser.setFileFilter(tempFilter);
        tempChooser.setSelectedFile(new File("*.dat"));
        tempChooser.setAccessory(null);
        if (tempChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            for (File file : tempChooser.getSelectedFiles()) {
                try {
                    Template temp = device.loadTemplate(file);
                    if (temps.contains(temp)) {
                        addLog("該指紋(" + temp.toString() + ")已經存在，無須重覆載入");
                    } else {
                        temps.add(temp);
                        cbTemplates.addItem(temp);
                    }
                    cbTemplates.setSelectedItem(temp);
                } catch (Exception ex) {
                    addLog(ex.getMessage());
                    logger.error(ex.getMessage(), ex);
                }
            }
        }
    }//GEN-LAST:event_btnAddTempActionPerformed

    private void btnSaveImageActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveImageActionPerformed
        if (inEnrolling.get()) {
            addLog("存圖: 登錄進行中，無法存圖");
        } else if (SFRDevice.isCaptured()) {
            tempChooser.setDialogType(JFileChooser.SAVE_DIALOG);
            tempChooser.setDialogTitle("請選擇圖檔要儲存的位置");
            tempChooser.setMultiSelectionEnabled(false);
            tempChooser.resetChoosableFileFilters();
            tempChooser.setFileFilter(gifFilter);
            tempChooser.setSelectedFile(new File("指紋.gif"));
            tempChooser.setAccessory(extMode);
            if (tempChooser.showSaveDialog(DemoMain.this) == JFileChooser.APPROVE_OPTION) {
                try {
                    File f = tempChooser.getSelectedFile();
                    if (tp.isAutoExt() && !f.getName().toLowerCase().endsWith(".gif")) {
                        f = new File(f.getPath() + ".gif");
                    }
                    if (!f.exists() || (JOptionPane.showConfirmDialog(getContentPane(),
                            String.format("輸出檔案:%s 已存在，是否覆寫?", f.getName()), "檔案覆寫?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)) {
                        device.saveGif(f);
                    }
                } catch (Exception ex) {
                    logger.error(ex.getMessage(), ex);
                    addLog(ex.getMessage());
                    device.clear();
                }
            } else {
                btnSaveImage.setEnabled(true);
            }
        } else {
            final java.awt.event.ActionEvent event = evt;
            device.abortCapturing();
            Thread action = new Thread() {

                @Override
                public void run() {
                    btnSaveImage.setEnabled(false);
                    logger.debug("存圖: 執行掃瞄");
                    device.clear();
                    addLog("存圖: 請放上手指，準備進行掃瞄");
                    if (device.capture()) {
                        fingerCanvas.repaint();
                        btnSaveImageActionPerformed(event);
                    } else {
                        addLog("存圖: 掃瞄失敗");
                    }
                }
            };
            action.start();
        }
    }//GEN-LAST:event_btnSaveImageActionPerformed

    private void btnSaveTemplateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveTemplateActionPerformed
        if (inEnrolling.get()) {
            addLog("存指紋: 登錄進行中，無法存指紋");
        } else if (temps.isEmpty()) {
            addLog("請先載入指紋資料或登錄指紋資料");
        } else if (cbTemplates.getSelectedItem() == null) {
            addLog("請先選指要儲存的指紋資料");
            cbTemplates.requestFocus();
        } else {
            Template temp = (Template) cbTemplates.getSelectedItem();
            tempChooser.setDialogType(JFileChooser.SAVE_DIALOG);
            tempChooser.setDialogTitle("請選擇指紋檔要儲存的位置");
            tempChooser.resetChoosableFileFilters();
            tempChooser.setFileFilter(tempFilter);
            tempChooser.setSelectedFile(new File(temp.getIdentity() + "-" + temp.getIndex() + ".dat"));
            tp.setTempName(temp.getIdentity());
            tp.setTempIndex(temp.getIndex());
            tp.repaint();
            tempChooser.setAccessory(tp);
            if (tempChooser.showSaveDialog(DemoMain.this) == JFileChooser.APPROVE_OPTION) {
                try {
                    File f = tempChooser.getSelectedFile();
                    temp.setIdentity(tp.getTempName());
                    temp.setIndex(tp.getTempIndex());
                    if (tp.isAutoExt() && !f.getName().toLowerCase().endsWith(".dat")) {
                        f = new File(f.getPath() + ".dat");
                    }
                    if (!f.exists() || (JOptionPane.showConfirmDialog(getContentPane(),
                            String.format("輸出檔案:%s 已存在，是否覆寫?", f.getName()), "檔案覆寫?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)) {
                        device.saveTemplate(temp, f);
                        cbTemplates.repaint();
                    }
                } catch (Exception ex) {
                    logger.error(ex.getMessage(), ex);
                    addLog(ex.getMessage());
                    device.clear();
                }
            } else {
                btnSaveTemplate.setEnabled(true);
            }
        }
    }//GEN-LAST:event_btnSaveTemplateActionPerformed

    private void btnIdentityActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnIdentityActionPerformed
        if (temps.isEmpty()) {
            iconIdentity.setIcon(null);
            addLog("沒有指紋可供辨識，請先載入或登錄新的指紋");
        } else if (inEnrolling.get()) {
            addLog("辨識:登錄尚在進行中，無法執行辨識");
        } else {
            Thread action = new Thread() {

                @Override
                public void run() {
                    device.abortMatching();
                    btnIdentity.setEnabled(false);
                    addLog("辨識:請放入手指");
                    device.clear();
                    if (device.capture()) {
                        try {
                            fingerCanvas.repaint();
                            SecurityLevel sl = SecurityLevel.values()[cbSecurityLavel.getSelectedIndex()];
                            Template temp = null;
                            long start = System.currentTimeMillis();
                            temp = device.identify(temps, sl, chkDetectCore.isSelected(), device.getTimeout(), temps.size() > 0 && chkThread.isSelected());
                            start = System.currentTimeMillis() - start;
                            if (temp != null) {
                                cbTemplates.setSelectedItem(temp);
                                cbTemplates.repaint();
                                iconIdentity.setIcon(match);
                                addLog("辨識: 品質(" + device.getEnrollQuality() + ")符合 - " + temp.toString() + ",共費 " + start + " ms");
                            } else {
                                cbTemplates.setSelectedIndex(-1);
                                iconIdentity.setIcon(wrong);
                                addLog("辨識: 沒有符合的指紋");
                            }

                        } catch (EnrollException ex) {
                            logger.error(ex.getMessage(), ex);
                            addLog(ex.getMessage());
                            iconIdentity.setIcon(wrong);
                        }
                    } else {
                        addLog("辨識:掃瞄失敗");
                    }
                }
            };
            action.start();
        }
    }//GEN-LAST:event_btnIdentityActionPerformed

    private void btnEnrollActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnEnrollActionPerformed
        device.abortCapturing();
        btnEnroll.setEnabled(false);
        addLog("登錄:請放入手指");
        Thread action = new Thread() {

            @Override
            public void run() {
                if (!inEnrolling.getAndSet(true) && device.capture()) {
                    try {
                        fingerCanvas.repaint();
                        Template temp = device.enroll(chkDetectCore.isSelected());
                        addLog("登錄: 取樣品質為 " + device.getEnrollQuality());
                        if (chkDoubleCheck.isSelected()) {
                            long wait = cbTimeout.getSelectedIndex() == 0 ? 10000 : cbTimeout.getSelectedIndex() * 1000;
                            if (device.isFingerOn()) {
                                addLog("登錄:請挪開手指");
                                long start = System.currentTimeMillis();
                                while (device.isFingerOn() && System.currentTimeMillis() - start < wait) {
                                }
                                if (device.isFingerOn()) {
                                    throw new RuntimeException("登錄:遲遲不肯挪開手指，無法進行再次確認");
                                }
                            }
                            addLog("登錄:請將要登錄的手指再次放入");
                            long start = System.currentTimeMillis();
                            while (!device.isFingerOn() && System.currentTimeMillis() - start < wait) {
                            }
                            if (!device.isFingerOn()) {
                                throw new RuntimeException("登錄:遲遲不肯放入手指，無法進行再次確認");
                            }
                            if (device.capture()) {
                                fingerCanvas.repaint();
                                temp = device.enrollWithVerify(temp, chkDetectCore.isSelected());
                                if (temp == null) {
                                    addLog("登錄: 兩次掃瞄指紋比對不吻合");
                                } else {
                                    addLog("登錄: 取樣品質為 " + device.getEnrollQuality());
                                }
                            }
                        }
                        if (temp != null) {
                            temp.setIdentity(String.format("%tT", Calendar.getInstance()));
                            temp.setIndex(cbTemplates.getItemCount() + 1);
                            temps.add(temp);
                            cbTemplates.addItem(temp);
                            cbTemplates.setSelectedItem(temp);
                            cbTemplates.repaint();
                        }
                    } catch (Exception ex) {
                        logger.error(ex.getMessage(), ex);
                        addLog(ex.getMessage());
                    } finally {
                        inEnrolling.set(false);
                        btnEnroll.setEnabled(true);
                    }
                } else {
                    addLog("登錄:掃瞄失敗");
                    inEnrolling.set(false);
                    btnEnroll.setEnabled(true);
                }
            }
        };
        action.start();
    }//GEN-LAST:event_btnEnrollActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        SFRDevice.unload();
    }//GEN-LAST:event_formWindowClosing

    private void cbTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbTypeActionPerformed
        if (isVisible()) {
            int index = cbType.getSelectedIndex();
            if (device.setSelectedDevice((DeviceType) cbType.getSelectedItem())) {
                SFRDevice.unload();
                initDevice();
            } else {
                addLog("設定型別:" + cbType.getSelectedItem().toString() + "失敗");
                cbType.setSelectedIndex(index);
            }
        }
    }//GEN-LAST:event_cbTypeActionPerformed

    private void chkFastModeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkFastModeActionPerformed
        SFRDevice.setFastMode(chkFastMode.isSelected());
        addLog("快速模式:" + (SFRDevice.isFastMode() ? "啟用" : "停用"));
    }//GEN-LAST:event_chkFastModeActionPerformed

    private void btnClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnClearActionPerformed
        device.clear();
        fingerCanvas.repaint();
        btnClear.setEnabled(false);
    }//GEN-LAST:event_btnClearActionPerformed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnAddTemp;
    private javax.swing.JButton btnClear;
    private javax.swing.JButton btnEnroll;
    private javax.swing.JButton btnIdentity;
    private javax.swing.JButton btnSaveImage;
    private javax.swing.JButton btnSaveTemplate;
    private javax.swing.JButton btnTest;
    private javax.swing.JButton btnVerify;
    private java.awt.Button button1;
    private javax.swing.JComboBox cbMachine;
    private javax.swing.JComboBox cbQuality;
    private javax.swing.JComboBox cbSecurityLavel;
    private javax.swing.JComboBox cbTemplates;
    private javax.swing.JComboBox cbTimeout;
    private javax.swing.JComboBox cbType;
    private javax.swing.JCheckBox chkDetectCore;
    private javax.swing.JCheckBox chkDoubleCheck;
    private javax.swing.JCheckBox chkFastMode;
    private javax.swing.JCheckBox chkThread;
    private javax.swing.JPanel enrollPanel;
    private java.awt.Canvas fingerCanvas;
    private javax.swing.JLabel iconIdentity;
    private javax.swing.JLabel iconVerify;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabel7;
    private javax.swing.JLabel jLabel8;
    private javax.swing.JLabel jLabel9;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.ImageIcon match;
    private javax.swing.JTextArea msgArea;
    private javax.swing.JSpinner spinBright;
    private javax.swing.JSpinner spinSensity;
    private javax.swing.JFileChooser tempChooser;
    private javax.swing.JTextField tfSerialNo;
    private javax.swing.ImageIcon wrong;
    // End of variables declaration//GEN-END:variables

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                new DemoMain(SFRDevice.getInstance()).setVisible(true);
            }
        });
    }
}
